module dice.render.painter;

import std.stdio;
import dice.render.shape;

import cr = dice.render.cairo;
import st = dice.render.structs;
import mx = dice.render.matrix;

import dice.render.pattern;
import dice.render.canvas;

alias st.Colour Colour;
alias mx.Matrix Matrix;

/** A class used for painting graphics */
abstract class Painter : Tracer
{
	/** Gets a disposable painter, that can be used for measurements and such */
	static Painter disposable ()
	{
		return (new Pixmap(1,1)).painter;
	}
	
	/* Colour, pattern, alpha */
	/** Sets the current colour */
	abstract void colour (Colour c);
	/** Sets the current pattern */
	abstract void pattern (Pattern p);
	
	/** Sets the current alpha limit.
	 * 
	 *  Note: This isn't implemented in the Cairo backend!
	 */
	abstract void alpha (float a);
	
	/* Save/restore */
	/** Save current state */
	abstract void save ();
	
	/** Restore previous state */
	abstract void restore ();
	
	/* Transformation matrix */
	/** Multiply the current matrix by m */
	abstract void matrix (Matrix m);
	
	/** Destroys the painter, [possibly] making it unusable but freeing resources */
	abstract void destroy ();
	
	/* Width */
	/** Set the line width for strokes */
	abstract void width (float w);
	
	/* Drawing */
	/** Trace a shape */
	void trace (Shape s)
	{
		s.traceOn(this);
	}
	
	/** Draw a line to given co-ordinates */
	abstract void line (float x, float y);
	/** Move current position to given co-ordinates */
	abstract void move (float x, float y);
	/** Draw a curve to x,y with control points x1,y1 and x2,y2 */
	abstract void curve (float x1, float y1, float x2, float y2, float x, float y);
	/** Close the path (draw a line back to the start if required) */
	abstract void close ();
	
	/** Colour in the border of the current path */
	abstract void stroke ();
	/** Like stroke, but leave the path info */
	abstract void strokePreserve ();
	/** Colour in the inside of the current path */
	abstract void fill ();
	/** Like fill, but leave the path info */
	abstract void fillPreserve ();
	/** Clip all drawing operations to the area inside the current path */
	abstract void clip ();
	/** Like clip, but leave the path info */
	abstract void clipPreserve ();
	/** Fill the entire canvas [after clipping] */
	abstract void fillAll();
}

/** An implementation of Painter, based on Cairo */
class CairoPainter : Painter
{
	cr.Context* handle;
	
	Canvas canvas;
	
	struct Status
	{
		float alpha;
	}
	
	//Status[] stack;
	
	this (Canvas c, cr.Context* h)
	{
		canvas = c;
		handle = h;
		//stack = [Status(1.0)];
	}
	
	void alpha (float a)
	{
	}
	
	/+
	/* Alpha, colour, matrix */
	void alpha (float a)
	{
		stack[0].alpha *= a;
	}+/
	
	void colour (Colour c)
	{
		cr.cairo_set_source_rgba(handle, c.r, c.g, c.b, c.a);//*stack[0].alpha);
	}
	
	void width (float w)
	{
		cr.cairo_set_line_width(handle, w);
	}
	
	void pattern (Pattern p)
	{
		Gradient g = cast(Gradient)p;
		if (g != null)
		{
			auto x = cr.cairo_pattern_create_linear(g.x1, g.y1, g.x2, g.y2);
			foreach (Gradient.Node y; g.nodes)
				cr.cairo_pattern_add_color_stop_rgba(x, y.point, y.colour.r, y.colour.g, y.colour.b, y.colour.a);
			cr.cairo_set_source(handle, x);
			cr.cairo_pattern_destroy(x);
			return;
		}
		
		PixmapPattern pp = cast(PixmapPattern)p;
		if (pp != null)
		{
			auto s = pp.pixmap.cairoClone;
			auto x = cr.cairo_pattern_create_for_surface(s);
			cr.cairo_set_source(handle, x);
			cr.cairo_surface_destroy(s);
			cr.cairo_pattern_destroy(x);
			return;
		}
		
		throw new Exception("pattern=" ~ p.toString ~ " - only PixmapPattern and LinearGradient right now!");
	}
	
	void matrix (Matrix m)
	{
		cr.cairo_transform(handle, &m.toCairo());
	}
	
	/* Save/restore */
	int saves=0;
	
	void save ()
	{
		/* Copy the top stack element */
		//stack = stack[0] ~ stack;
		
		saves++;
		
		cr.cairo_save(handle);
	}
	
	void restore ()
	{
		saves--;
		//stack = stack[1..stack.length];
		cr.cairo_restore(handle);
	}
	
	/* Destructor */
	bool destroyed = false;
	
	void destroy ()
	{
		if (destroyed)
			return;
		
		destroyed = true;
		handle = null;
		
		/+if (stack.length != 1)
		{
			writefln("-WARNING- unbalanced save/restore on CairoPainter!");
		}+/
		cr.cairo_destroy(handle);
	}
	
	~this ()
	{
		destroy();
	}
	
	/* Drawing */
	void line (float x, float y)
	{
		cr.cairo_line_to(handle, x, y);
	}
	
	void curve (float x1, float y1, float x2, float y2, float x, float y)
	{
		cr.cairo_curve_to(handle, x1, y1, x2, y2, x, y);
	}
	
	void move (float x, float y)
	{
		cr.cairo_move_to(handle, x, y);
	}
	
	void close ()
	{
		cr.cairo_close_path(handle);
	}
	
	void stroke ()
	{
		cr.cairo_stroke(handle);
	}
	
	void strokePreserve ()
	{
		cr.cairo_stroke_preserve(handle);
	}
	
	void fill ()
	{
		cr.cairo_fill(handle);
	}
	
	void fillPreserve ()
	{
		cr.cairo_fill_preserve(handle);
	}
	
	void clip ()
	{
		cr.cairo_clip(handle);
	}
	
	void clipPreserve ()
	{
		cr.cairo_clip_preserve(handle);
	}
	
	void fillAll ()
	{
		cr.cairo_paint(handle);//, stack[0].alpha);
	}
}
